package com.newmainsoftech.dao.entity.relation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import javax.persistence.OneToMany;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of <code>{@link BiLinkCollectionProxy}</code> interface. </br >
 * Synchronization for this is handled by <code>{@link EntityBiLinkCollectionProxyImplAspect}</code> 
 * aspect.
 * 
 * @author Arata Y.
 *
 * @param <R>
 * @param <E>
 */
public class EntityBiLinkCollectionProxyImpl<R extends HolderSide<E>, E extends HoldeeSide<R>> 
implements BiLinkCollectionProxy<R, E> {
	private Logger logger = LoggerFactory.getLogger( getClass());
		protected Logger getLogger() {
			return logger;
		}
		
	private R holderEntity;
		public R getHolderEntity() {
			return holderEntity;
		}
		/**
		 * Set holder entity what holds holdee entity collection (what can be obtained with 
		 * <code>{@link HolderSide#getHoldees()}</code> method).<br />
		 * This also nullify flag indicating orphanRemoval attribute value; so later when   
		 * <code>{@link #isOrphanRemovalAttributeOn()}</code> method is called, that flag value can be 
		 * read from holder entity again.<br />
		 * 
		 * @param holderEntity
		 */
		protected void setHolderEntity( final R holderEntity) {
			if ( holderEntity == null) {
				throw new IllegalArgumentException(
						String.format(
								"Value of %1$s argument cannot be null but an %2$s instance.",
								"holderEntity",
								HolderSide.class.getSimpleName())
						);
			}
			this.holderEntity = holderEntity;
			initOrphanRemovalAttributeFlag();
		}
	private R defaultHolderEntity = null;
		public R getDefaultHolderEntity() {
			return defaultHolderEntity;
		}
		protected void setDefaultHolderEntity( final R defaultHolderEntity) {
			if ( defaultHolderEntity != null) {
				if ( defaultHolderEntity.equals( getHolderEntity())) {
					throw new IllegalArgumentException(
							String.format(
									"%1$s has already been set as holder entity; default holder entity " 
									+ "must be different from holder entity.",
									defaultHolderEntity)
							);
				}
				if ( isOrphanRemovalAttributeOn()) {
					throw new IllegalStateException(
							String.format(
									"Blocking to set %1$s as default holder because it would have broken " 
									+ "private relationship; JPA spec bans assigning orphan holdee entity " 
									+ "to relationship with another holder entity (default holder in this " 
									+ "case) while orphanRemoval attribute has been set to true, and " 
									+ "it has been set to true in %2$s class.",
									defaultHolderEntity,
									getHolderEntity().getClass().getSimpleName())
							);
				}
			}
			
			this.defaultHolderEntity = defaultHolderEntity;
		}
	
	private Constructor<Collection<E>> alterWorkerCollectionConstructor;
		public Constructor<Collection<E>> getAlterWorkerCollectionConstructor() {
			return alterWorkerCollectionConstructor;
		}
		@SuppressWarnings( value = { "unchecked"})
		protected void setAlterWorkerCollectionConstructor(
				final Constructor<Collection<E>> alterWorkerCollectionConstructor) {
			if ( alterWorkerCollectionConstructor != null) {
				this.alterWorkerCollectionConstructor = alterWorkerCollectionConstructor;
			}
			else {
				try {
					Collection<E> tempLinkedList = new LinkedList<E>();
					this.alterWorkerCollectionConstructor
					= (Constructor<Collection<E>>)(
							tempLinkedList.getClass()
								.getConstructor( new Class<?>[]{ Collection.class}));
				}
				catch( Exception exception) {
					throw new RuntimeException(
							String.format(
									"Failure in re-setting constructor of %1$s class with a collection " 
									+ "instance argument as default value of %2$s member field.",
									LinkedList.class.getSimpleName(),
									"alterWorkerCollectionConstructor"),
							exception);
				}
			}
			this.alterWorkerCollectionConstructor.setAccessible( true);
		}
		public Collection<E> createAlterWorkerCollectionInstance( final Collection<E> holdees) {
			try {
				return getAlterWorkerCollectionConstructor().newInstance( holdees);
			}
			catch( Exception exception) {
				throw new RuntimeException(
						String.format(
								"Failure in instantiating a %1$s type instance via %2$s constructor with " 
								+ "single %3$s argument.",
								getAlterWorkerCollectionConstructor().getDeclaringClass().getSimpleName(),
								getAlterWorkerCollectionConstructor().toString(),
								holdees),
						exception);
			}
		}
	
	private Method holdeesGetterMethod;
		protected Method getHoldeesGetterMethod() {
			if ( holdeesGetterMethod == null) {
				try {
					holdeesGetterMethod 
					= HolderSide.class.getDeclaredMethod( "getHoldees", new Class<?>[]{});
				}
				catch( Exception exception) {
					if ( RuntimeException.class.isInstance( exception)) {
						throw (RuntimeException)exception;
					}
					else {
						throw new RuntimeException( exception);
					}
				}
			}
			return holdeesGetterMethod;
		}
	
	private Boolean isOrphanRemovalAttributeOn = null;
		/* Initialize (nullify) flag indicating orphanRemoval attribute value.
		 */
		void initOrphanRemovalAttributeFlag() {
			isOrphanRemovalAttributeOn = null;
		}
		/**
		 * Set boolean value being used to select simpler logic being executed in 
		 * <code>{@link #invoke(Object, Method, Object[])}</code> method.<br />
		 * If <code>orphanRemoval</code> attribute value of <code>OneToMany</code> annotation is 
		 * turned on for the relation between holder <code>{@link HolderSide}</code> entity and holdee 
		 * <code>{@link HoldeeSide}</code> entity, and you are going to discard that holdee entity object, 
		 * then you can have benefit of better performance by giving true to this method. 
		 * 
		 * @param isOrphanRemovalAttributeOn
		 * @see #isOrphanRemovalAttributeOn()
		 * @see #readOrphanRemovalAttribute(HolderSide)
		 * @see #EntityMediatorCollectionProxyImpl(HolderSide, HolderSide, Boolean, Class)
		 * @see #EntityMediatorCollectionProxyImpl(HolderSide, HolderSide, Boolean)
		 * @see #EntityMediatorCollectionProxyImpl(HolderSide, Boolean)
		 */
		protected void setOrphanRemovalAttributeOn( boolean isOrphanRemovalAttributeOn) {
			this.isOrphanRemovalAttributeOn = isOrphanRemovalAttributeOn;
		}
		/**
		 * Return <code>Boolean</code> value being used to select simpler logic being executed in 
		 * <code>{@link #invoke(Object, Method, Object[])}</code> method for the case that 
		 * <code>orphanRemoval</code> attribute value of <code>OneToMany</code> annotation for 
		 * the relation between holder <code>{@link HolderSide}</code> entity and holdee 
		 * <code>{@link HoldeeSide}</code> entity since, after all, holdee entity will be removed 
		 * from datasource when it is removed from the collection if orphan removal has been turned on, 
		 * so therefore it can be not necessary to update any holdee entity state.  
		 * 
		 * @return <code>Boolean</code> value being used to select simpler logic being executed in 
		 * <code>{@link #invoke(Object, Method, Object[])}</code> method.<br />
		 * When its value is null, then that means it has not been set yet. 
		 * @see #readOrphanRemovalAttribute(HolderSide)
		 */
		public boolean isOrphanRemovalAttributeOn() {
			Boolean isOrphanRemovalAttributeOnObj = isOrphanRemovalAttributeOn;
			if ( !Boolean.class.isInstance( isOrphanRemovalAttributeOnObj)) {
				isOrphanRemovalAttributeOnObj 
				= readOrphanRemovalAttribute( getHolderEntity());
				setOrphanRemovalAttributeOn( isOrphanRemovalAttributeOnObj.booleanValue());
			}
			return isOrphanRemovalAttributeOnObj.booleanValue();
		}
		/**
		 * Attempts to read <code>orphanRemoval</code> attribute value of <code>OneToMany</code> 
		 * annotation defined for holdee collection being returned by 
		 * <code>{@link HolderSide#getHoldees()}</code> method on value of <code>holderEntity</code>  
		 * argument.<br />
		 * This attempts to read it from the next places in the shown order: 
		 * <ol>
		 *  <li>Collection member field holding the same collection being returned by 
		 *  <code>{@link HolderSide#getHoldees()}</code> method.
		 *  </li>
		 *  <li>Overridden <code>{@link HolderSide#getHoldees()}</code> method.
		 *  </li>
		 * </ol>
		 * This returns false when this cannot find the <code>OneToMany</code> annotation by any reason 
		 * including hitting any exception in reflective access. 
		 * 
		 * @param holderEntity
		 * @return
		 */
		protected boolean readOrphanRemovalAttribute( R holderEntity) {
			if ( holderEntity == null) {
				throw new IllegalArgumentException(
						String.format(
								"Value of %1$s argument must be a %2$s instance but null.",
								"holderEntity", 
								HolderSide.class.getSimpleName())
						);
			}
			
			final Collection<E> holdeeCollection = holderEntity.getHoldees();
				if ( holdeeCollection == null) {
					throw new NullPointerException( 
							String.format(
									"%1$s.%2$s method on %3$s returned null but a %4$s instance.",
									getHoldeesGetterMethod().getDeclaringClass().getSimpleName(), 
									getHoldeesGetterMethod().getName(), 
									holderEntity, 
									Collection.class.getSimpleName())
							);
				}
				
			Boolean isOrphanRemovalAttributeOn = null;
				final Class<?> holdeeCollectionClass = holdeeCollection.getClass();
				for( 	Class<?> classObj = holderEntity.getClass(); 
						classObj != null; 
						classObj = classObj.getSuperclass()) {
					try {
						for( Field field : classObj.getDeclaredFields()) {
							Class<?> fieldType = field.getType();
							if ( !fieldType.isAssignableFrom( holdeeCollectionClass) 
									&& !holdeeCollectionClass.isAssignableFrom( fieldType)) continue;
							field.setAccessible( true);
							if ( holdeeCollection != field.get( holderEntity)) continue;

							OneToMany oneToMany = field.getAnnotation( OneToMany.class);
								if ( oneToMany == null) continue;
							isOrphanRemovalAttributeOn = oneToMany.orphanRemoval();
							break;
						}
					}
					catch( Exception exception) {
						Logger logger = getLogger();
							if ( logger.isDebugEnabled()) {
								logger.debug(
										String.format(
												"Failure in identifing member field holding %1$s collection " 
												+ "instance on %2$S object and obtaining orphanRemoval " 
												+ "attribute value of %3$s annotation on that member field.",
												holdeeCollection, 
												holderEntity, 
												OneToMany.class.getSimpleName()),
										exception);
							}
					}
					if ( isOrphanRemovalAttributeOn != null) break;
				}
				if ( isOrphanRemovalAttributeOn == null) {
					for( 	Class<?> classObj = holderEntity.getClass(); 
							classObj != null; 
							classObj = classObj.getSuperclass()) {
						final String holdeesGetterMethodName = getHoldeesGetterMethod().getName();
						try {
							for( Method method : classObj.getDeclaredMethods()) {
								if ( !method.getName().equals( holdeesGetterMethodName)) continue;
								if ( method.getParameterTypes().length > 0) continue;
								
								Class<?> returnType = method.getReturnType();
								if ( !returnType.isAssignableFrom( holdeeCollectionClass)
										&& !holdeeCollectionClass.isAssignableFrom( returnType)) continue;
								method.setAccessible( true);
								Object returnedObj = method.invoke( holderEntity, new Object[]{});
								if ( holdeeCollection != returnedObj) continue;
								
								OneToMany oneToMany = method.getAnnotation( OneToMany.class);
									if ( oneToMany == null) continue;
								isOrphanRemovalAttributeOn = oneToMany.orphanRemoval();
								break;
							}
						}
						catch( Exception exception) {
							Logger logger = getLogger();
								if ( logger.isDebugEnabled()) {
									logger.debug(
											String.format(
													"Failure in detecting overriden %1$s.%2$s method in " 
													+ "chain of class hierarchy from %3$s in order to " 
													+ "obtain orphanRemoval attribute value of %4$s " 
													+ "annotation on that method.",
													getHoldeesGetterMethod().getDeclaringClass().getSimpleName(),
													holdeesGetterMethodName,
													holderEntity.getClass().getSimpleName(), 
													OneToMany.class.getSimpleName()),
											exception);
								}
						}
						if ( isOrphanRemovalAttributeOn != null) break;
					}
				}
			return ((isOrphanRemovalAttributeOn == null) ? false : isOrphanRemovalAttributeOn); 
		}
	public static final boolean IsOkToNullifyHolderAtRemovalDefaultValue = false;
		public static boolean getIsOkToNullifyHolderAtRemovalDefaultValue() {
			return IsOkToNullifyHolderAtRemovalDefaultValue;
		}

	private boolean isOkToNullifyHolderAtRemoval = IsOkToNullifyHolderAtRemovalDefaultValue;
		/**
		 * Get value of switch controlling with <code>{@link #isOrphanRemovalAttributeOn()}</code> method 
		 * return value whether set null as default holder on holdee entity at removal of holdee entity.<br />
		 * Default to {@value #IsOkToNullifyHolderAtRemovalDefaultValue}.<br />
		 * Null will be set as holder on holdee entity at removal of holdee entity out of holder only 
		 * when this switch is turned on and orphanRemoval attribute has been set to false in holder 
		 * class. <br />
		 * Such switch was added as work around the JPA provider implementation difference based on  
		 * interpretation over orphanRemoval attribute of JPA spec; 
		 * see <a href="https://java.net/jira/browse/JPA_SPEC-33">JPA SPEC 33</a>.<br /> 
		 * Please be advised that, when true is set, merging holdee entity after cutting relation between 
		 * holder and holdee will not delete holdee entity by orphan removal even it is originally 
		 * bi-directional relation, because there is, of course, no upward link from holdee to holder. 
		 * That may offend the private relationship requirement.<br />
		 * 
		 * @return value of switch controlling with <code>{@link #isOrphanRemovalAttributeOn()}</code> method 
		 * return value whether set null as default holder on holdee entity at removal of holdee entity.
		 */
		public boolean isOkToNullifyHolderAtRemoval() {
			return isOkToNullifyHolderAtRemoval;
		}
		/**
		 * Set value of switch controlling with <code>{@link #isOrphanRemovalAttributeOn()}</code> method 
		 * return value whether set null as default holder on holdee entity at removal of holdee entity.<br />
		 * Default to {@value #IsOkToNullifyHolderAtRemovalDefaultValue}.<br />
		 * Null will be set as holder on holdee entity at removal of holdee entity out of holder only 
		 * when this switch is turned on and orphanRemoval attribute has been set to false in holder 
		 * class. <br />
		 * Such switch was added as work around the JPA provider implementation difference based on  
		 * interpretation over orphanRemoval attribute of JPA spec; 
		 * see <a href="https://java.net/jira/browse/JPA_SPEC-33">JPA SPEC 33</a>.<br /> 
		 * Please be advised that, when true is set, merging holdee entity after cutting relation between 
		 * holder and holdee will not delete holdee entity by orphan removal even it is originally 
		 * bi-directional relation, because there is, of course, no upward link from holdee to holder. 
		 * That may offend the private relationship requirement.<br />
		 * 
		 * @param isOkToNullifyHolderAtRemoval When this is true and orphanRemoval attribute has been 
		 * set to false in holder class (<code>{@link #isOrphanRemovalAttributeOn()}</code> method 
		 * returns false), then null will be set as holder on holdee entity at removal of holdee entity 
		 * out of holder.
		 */
		public void setOkToNullifyHolderAtRemoval( boolean isOkToNullifyHolderAtRemoval) {
			this.isOkToNullifyHolderAtRemoval = isOkToNullifyHolderAtRemoval;
		}

	// Constructors -------------------------------------------------------------------------------
	public EntityBiLinkCollectionProxyImpl( final R holderEntity, final R defeaultHolderEntity) {
		if ( holderEntity == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument cannot be null but an %2$s instance.",
							"holderEntity",
							HolderSide.class.getSimpleName())
					);
		}
		if ( holderEntity.equals( defeaultHolderEntity)) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument should not equal to one provided with %2$s " 
							+ "argument but they are identical: %3$s and %4$s.",
							"defeaultHolderEntity", 
							"holderEntity",
							defeaultHolderEntity,
							holderEntity)
					);
		}
		setHolderEntity( holderEntity);
			// setHolderEntity method also nullify isOrphanRemovalAttributeOn member field.
		setDefaultHolderEntity( defeaultHolderEntity);
	}
	
	public EntityBiLinkCollectionProxyImpl( final R holderEntity) {
		this( holderEntity, null);
	}
	// --------------------------------------------------------------------------------------------
	
	// EntityMediatorBaseCollectionProxy interface implementation ---------------------------------
	/**
	 * {@inheritDoc}
	 */
	@Override
	@SuppressWarnings( value = { "unchecked"})
	public Object interIterator( final Object proxy, final Method method) {
		HoldeeIteratorProxy holdeeIteratorProxy = new HoldeeIteratorCramptedProxyImpl<R, E>( this);
		Object iterator
		= Proxy.newProxyInstance( 
				this.getClass().getClassLoader(), 
				new Class[]{ Iterator.class}, 
				holdeeIteratorProxy);
		return (Iterator<E>)iterator;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object interAdd( final Object proxy, final Method method, final E holdeeArg) {
		R holder = getHolderEntity();
		
		if ( holdeeArg != null) {
			holdeeArg.ensureIdentity();
				// ensured that business identity of holdee entity has been populated 
				// before adding to collection.
			holdeeArg.setHolderEntity( holder);
				// ensured relation from holdee to holder before adding to collection.
		}
		
		final Collection<E> holdees = holder.getHoldees();
			if ( holdees == null) {
				throw new NullPointerException(
						String.format(
								"%1$s method on %2$s did not return an instance of collection but null.",
								getHoldeesGetterMethod(),
								holder)
						);
			}
		return holdees.add( holdeeArg);
	}
	
	/**
	 * <ul>
	 *  <li>When the value of <code>isOrphanRemovalAttributeOn</code> argument is false, then add 
	 *  	<code>holdee</code> to <code>holdeesWithDefaultHolder</code>, and set <code>defaultHolder</code> 
	 *  	as the holder of <code>holdee</code>.
	 *  </li>
	 *  <li>When the value of <code>isOrphanRemovalAttributeOn</code> argument is true and the value of 
	 *  	<code>isOkToNullifyHolderAtRemoval</code> argument is true, then set <code>null</code> as 
	 *  	the holder of <code>holdee</code>.
	 *  </li>
	 * </ul>
	 * 
	 * @param method
	 * @param holder
	 * @param holdee
	 * @param defaultHolder
	 * @param holdeesWithDefaultHolder
	 * @param isOrphanRemovalAttributeOn
	 * @param isOkToNullifyHolderAtRemoval
	 */
	protected void linkHoldeeWithDefaultHolder(
			final Method method, 
			final R holder, 
			final E holdee, 
			final R defaultHolder, 
			final Collection<E> holdeesWithDefaultHolder,
			final boolean isOrphanRemovalAttributeOn, 
			final boolean isOkToNullifyHolderAtRemoval) {
		
		final Logger logger = getLogger();
		
		if ( !isOrphanRemovalAttributeOn) {
			if ( ( holdee != null) && holdee.equals( defaultHolder)) {
				if ( logger.isInfoEnabled()) {
					final String methodName = method.getName();
					logger.info(
							String.format(
									"Prevented from setting %1$s as holder of self in %2$s " 
									+ "operation on %3$s collection in %4$s.", 
									holdee,
									methodName,
									holdee.getClass().getSimpleName(),
									holder)
							);
				}
			}
			else {
				if ( holdeesWithDefaultHolder != null) {
					holdeesWithDefaultHolder.remove( holdee);
						// to assure no duplicate by different instance 
					holdeesWithDefaultHolder.add( holdee);
					
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Set downward link from default holder entity " 
											+ "to holdee entity after cutting downward link " 
											+ "from holder entity to holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s" 
											+ "%nDefault holder eneity: %3$s",
											holder,
											holdee,
											defaultHolder)
									);
						}
				}
				if ( holdee != null) {
					holdee.setHolderEntity( defaultHolder);
					
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Set upward link from holdee entity to default " 
											+ "holder entity after cutting relation between " 
											+ "holder entity and holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s" 
											+ "%nDefault holder eneity: %3$s",
											holder,
											holdee,
											defaultHolder)
									);
						}
				}
			}
		}
		else {
			if ( isOkToNullifyHolderAtRemoval) {
				if ( holdee != null) {
					holdee.setHolderEntity( null);
					
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Nullified upward link to holder entity on holdee " 
											+ "entity after cutting relation between " 
											+ "holder entity and holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s", 
											holder,
											holdee)
									);
						}
				}
			}
			else {
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"Did not cut upward link from holdee entity to holder entity " 
									+ "by assigning null as holder on holdee entity because " 
									+ "orphanRemoval attribute has set to %1$b in %2$s holder " 
									+ "class and isOkToNullifyHolderAtRemoval switch value is %3$b."
									+ "%nHolder eneity: %4$s" 
									+ "%nHoldee entity: %5$s", 
									isOrphanRemovalAttributeOn,
									holder.getClass().getSimpleName(),
									isOkToNullifyHolderAtRemoval,
									holder,
									holdee)
							);
				}
			}
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 * Note: Please be advised that, if holdee object being provided with <code>holdeeArg</code> argument 
	 * is actually removed, the holdee object will have different holder object set in inside of this method:
	 * <ul>
	 *  <li>Cases that holder object will be changed on holdee object: 
	 *  	<ul>
	 *  	 <li>When default holder object having been set by 
	 *  		<code>{@link #setDefaultHolderEntity(HolderSide)}</code> method is different with 
	 *  		holder object having already set on holdee object, then default holder object will be 
	 *  		set on holdee object.  
	 *  	 </li>
	 *  	 <li>
	 *  	 </li>
	 *  	</ul>
	 *  </li>
	 *  <li>Cases that holder object will not be changed on holdee object
	 *  	<ul>
	 *  	 <li>When holdee object is not removed, then there will be no holder object change on 
	 *  		holdee object. 
	 *  	 </li>
	 *  	 <li>When orphan removal attribute has been set to true on holder side, there will be no 
	 *  		holder object change on holdee object, in order to get along with intention of having 
	 *  		turned orphan removal attribute on (meaning expecting removal of holdee object from 
	 *  		datastore). 
	 *  	 </li>
	 *  	</ul>
	 *  </li>
	 * </ul>
	 * 
	 * I need either to implements logic of not allowing setting defaultHolderEntity when 
	 * isOrphanRemovalAttributeOn is true or decide preceding between isOrphanRemovalAttributeOn and 
	 * defaultHolderEntity, due to conflict between their intention.
	 * <ul>
	 *  <li>isOrphanRemovalAttributeOn is true<br />
	 *  	The intention of this expects holdee object will be removed from datastore.<br />
	 *  	Thereby, holder object should not be changed to default holder object (unless it's null) on 
	 *  	holdee object. Otherwise, holdee object will not be automatically removed from datastore. 
	 *  </li>
	 *  <li>defaultHolderEntity is not null<br />
	 *  	The intention of this expectes holder object will be changed to default holder object at 
	 *  	cutting downard link from holder object to holdee object.<br />
	 *  </li>
	 * </ul>
	 * 
	 */
	@Override
	public Object interRemove( final Object proxy, final Method method, final E holdeeArg) {
		R holder = getHolderEntity();
		boolean isOrphanRemovalAttributeOn = isOrphanRemovalAttributeOn();
		R defaultHolder = getDefaultHolderEntity();
		boolean isOkToNullifyHolderAtRemoval = isOkToNullifyHolderAtRemoval();

		final Collection<E> holdees = holder.getHoldees();
			if ( holdees == null) {
				throw new NullPointerException(
						String.format(
								"%1$s method on %2$s did not return an instance of collection but null.",
								getHoldeesGetterMethod(),
								holder)
						);
			}

		boolean removeResult = holdees.remove( holdeeArg);
			if ( removeResult) {
				Collection<E> holdeesWithDefaultHolder = null;
					if ( defaultHolder != null) {
						holdeesWithDefaultHolder = defaultHolder.getHoldees();
					}
				linkHoldeeWithDefaultHolder( 
						method, 
						holder, 
						holdeeArg, 
						defaultHolder, 
						holdeesWithDefaultHolder, 
						isOrphanRemovalAttributeOn, 
						isOkToNullifyHolderAtRemoval);
			}
		return removeResult;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object interAddAll( 
			final Object proxy, final Method method, final Collection<E> holdeeBunch) {
		if ( holdeeBunch == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument must be a %2$s instance but null.",
							"holdeeBunch", 
							Collection.class.getSimpleName())
					);
		}
		
		R holder = getHolderEntity();
		
		final Iterator<E> iterator = holdeeBunch.iterator();
			while( iterator.hasNext()) {
				E holdee = iterator.next();
					if ( holdee != null) {
						if ( holdee.equals( holder)) {
							iterator.remove();
							
							Logger logger = getLogger();
								if ( logger.isInfoEnabled()) {
									final String methodName = method.getName();
									logger.info(
											String.format(
													"Prevented from setting %1$s as holder of self in %2$s " 
													+ "operation on %3$s collection in %4$s.", 
													holdee,
													methodName,
													holdee.getClass().getSimpleName(),
													holder)
											);
								}
						}
						else {
							holdee.setHolderEntity( holder);	
								// This probably won't affect hash value but anything can happen
							holdee.ensureIdentity();	// This may change hash value of holdee
						}
					}
			}
			
		Collection<E> holdees = holder.getHoldees();
			if ( holdees == null) {
				throw new NullPointerException( 
						String.format(
								"%1$s method on %2$s did not return an instance of collection but null.",
								getHoldeesGetterMethod(),
								holder)
						);
			}
			
		return holdees.addAll( holdeeBunch);
	}

	/**
	 * {@inheritDoc}
	 * <ul>
	 *  <li>When the orphanRemoval attribute has not set to true and the default holder entity has 
	 *  	been set to non null value by <code>{@link #setDefaultHolderEntity(HolderSide)}</code> 
	 *  	method, the relation between that default entity and each holdee entity contained in 
	 *  	the <code>holdeeBunch</code> argument will be established, <u>no matter whether holdee 
	 *  	entity is removed</u>.
	 *  </li>
	 *  <li>When <code>{@link HolderSide#isPurgedHoldeesCollected()}</code> method on the holder 
	 *  	entity (what is obtained by <code>{@link #getHolderEntity()}</code> method) returns true, 
	 *  	this will yield collection of holdee entities purged out of holdee collection contained 
	 *  	in holder entity.
	 *  </li>
	 * </ul>
	 */
	@Override
	public Object interRemoveAll( 
			final Object proxy, final Method method, final Collection<E> holdeeBunch) {
		if ( holdeeBunch == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument must be a %2$s instance but null.",
							"holdeeBunch",
							Collection.class.getSimpleName())
					);
		}
		
		R holder = getHolderEntity();
		Collection<E> holdees = holder.getHoldees();
			if ( holdees == null) {
				throw new NullPointerException(
						String.format(
								"%1$s method on %2$s did not return an instance of collection but null.",
								getHoldeesGetterMethod(),
								holder)
						);
			}

		LinkedList<E> purgedHoldees = new LinkedList<E>();
			for( E holdee : holdeeBunch) {
				Boolean isRemoved = (Boolean)(interRemove( proxy, method, holdee));
				if ( isRemoved.booleanValue()) {
					purgedHoldees.add( holdee);
				}
			}
		if ( purgedHoldees.size() < 1) return false;
		holder.setPurgedHoldees( purgedHoldees);
		return true;
	}

	/**
	 * {@inheritDoc}
	 * When the orphanRemoval attribute has not set to true and the default holder entity has been set 
	 * to non null value by <code>{@link #setDefaultHolderEntity(HolderSide)}</code> method, the relation 
	 * between that default entity and each holdee entity (removed from the holdee collection returned 
	 * by <code>{@link HolderSide#getHoldees()}</code> method on holder entity returned by 
	 * <code>{@link #getHolderEntity()}</code> method) will be made. If the orphanRemoval attribute has 
	 * set to true and <code>{@link #isOkToNullifyHolderAtRemoval()}</code> method returns true, then 
	 * null will be assgined as holder on each holdee entity removed.<br />
	 * During <code>retainAll</code> operation on the holdee collection (returned by 
	 * <code>{@link HolderSide#getHoldees()}</code> method on holder entity returned by 
	 * <code>{@link #getHolderEntity()}</code> method), that holdee collection will be locked for 
	 * synchronization in order to calculate purged entities out of that collection. <br />
	 * Only when <code>retainAll</code> operation on the holdee collection (returned by 
	 * <code>{@link HolderSide#getHoldees()}</code> method on holder entity returned by 
	 * <code>{@link #getHolderEntity()}</code> method) returns true, 
	 * <code>{@link HolderSide#setPurgedHoldees(Collection)}</code> method on holder entity returned by 
	 * <code>{@link #getHolderEntity()}</code> method will be called to give collection of removed holdee 
	 * entity.<br />
	 *  
	 */
	@Override
	public Object interRetainAll( 
			final Object proxy, final Method method, final Collection<E> holdeeBunch) {
		if ( holdeeBunch == null) {
			throw new IllegalArgumentException(
					String.format(
							"Value of %1$s argument must be a %2$s instance but null.",
							Collection.class.getSimpleName())
					);
		}
		
		R holder = getHolderEntity();
		boolean isOrphanRemovalAttributeOn = isOrphanRemovalAttributeOn();
		R defaultHolder = getDefaultHolderEntity();
		boolean isOkToNullifyHolderAtRemoval = isOkToNullifyHolderAtRemoval();
		
		Collection<E> holdeesCopy = holder.getHoldees();
			if ( holdeesCopy == null) {
				throw new NullPointerException(
						String.format(
								"%1$s method on %2$s did not return an instance of collection but null.",
								getHoldeesGetterMethod(),
								holder)
						);
			}
			holdeesCopy.size();
			
		ArrayList<E> purgedHoldees = new ArrayList<E>( holdeesCopy);
		boolean retainAllResult = holdeesCopy.retainAll( holdeeBunch);
		if ( retainAllResult) {
			purgedHoldees.removeAll( holdeesCopy);
			
			holder.setPurgedHoldees( purgedHoldees);
		}
		
		return retainAllResult;
	}
	// --------------------------------------------------------------------------------------------
	// InvocationHandler interface implementation -------------------------------------------------
	/**
	 * Being called by <code>{@link #invoke(Object, Method, Object[])}</code> method to 
	 * invoke read-kind operation method on holdee collection (what will be obtained by 
	 * <code>{@link HolderSide#getHoldees()}</code> method on <code>holder</code> parameter).<br />
	 * 
	 * @param holder
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 * @see {@link EntityBiLinkCollectionProxyImplAspect#pointcutAtExecutionOfReadSynchronizingMethod()}
	 * @see {@link EntityBiLinkCollectionProxyImplAspect#aroundExecutionOfReadSynchronizingMethod(org.aspectj.lang.ProceedingJoinPoint)}
	 */
	protected Object invokeHoldeeCollectionNonExclusiveMethod( 
			final R holder, final Method method, final Object[] args) 
	throws Throwable {
		final Collection<E> holdees = holder.getHoldees();
			if ( holdees == null) {
				throw new NullPointerException(
						String.format(
								"Value being returned via method signature of %1$s in %2$s " 
								+ "must be a %3$s instance but null.",
								HolderSide.class.getSimpleName(),
								holder, 
								Collection.class.getSimpleName())
						);
			}
		return method.invoke( holdees, args);
	}
	
	@Override
	@SuppressWarnings( value = { "unchecked"})
	public Object invoke( final Object proxy, final Method method, final Object[] args)
	throws Throwable {
		final String methodName = method.getName();
		final Class<?>[] parameterTypes = method.getParameterTypes();
		
		switch( parameterTypes.length) {
		case 0:
			switch( methodName) {
			case "iterator":
				return interIterator( proxy, method);
			}
			break;
		case 1:
			switch( methodName) {
			case "add":
				return interAdd( proxy, method, (E)args[ 0]);
			case "remove":
				return interRemove( proxy, method, (E)args[ 0]);
			case "addAll":
				return interAddAll( proxy, method, (Collection<E>)args[ 0]);
			case "removeAll":
				return interRemoveAll( proxy, method, (Collection<E>)args[ 0]);
			case "retainAll":
				return interRetainAll( proxy, method, (Collection<E>)args[ 0]);
			}
			break;
		}
		
		return invokeHoldeeCollectionNonExclusiveMethod( getHolderEntity(), method, args);
	}
	// --------------------------------------------------------------------------------------------
}

//TODO implements way of rather using query than Collectin's method in interRemoveAll and interRetainAll methods for better performance.